/*
 * $Id: JMapGrid.java 52 2010-01-11 01:42:30Z aaron.day $
 */
package org.crazydays.gameplan.map.swing;


import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;

import javax.swing.Scrollable;
import javax.swing.SwingConstants;

import org.crazydays.gameplan.map.GridType;
import org.crazydays.gameplan.map.IGameMap;

/**
 * JMapGeometry
 */
public class JMapGrid
    extends Component
    implements Scrollable
{
    /** serial version uid */
    public final static long serialVersionUID =
        "$Id: JMapGrid.java 52 2010-01-11 01:42:30Z aaron.day $".hashCode();

    /** game map */
    protected IGameMap gameMap;

    /** max unit increment */
    protected int maxUnitIncrement = 1;

    /** lines */
    protected BufferedImage lines;

    /**
     * JMapGeometry constructor.
     * 
     * @param gameMap Game map
     */
    public JMapGrid(IGameMap gameMap)
    {
        setGameMap(gameMap);
        initialize();
    }

    /**
     * Initialize map geometry.
     */
    protected void initialize()
    {
        // initialize geometry
        setSize(getGameMap().getGeometry().getWidth(), getGameMap()
            .getGeometry().getHeight());

        lines =
            new BufferedImage(getGameMap().getGeometry().getWidth(),
                getGameMap().getGeometry().getHeight(),
                BufferedImage.TYPE_4BYTE_ABGR);

        initializeLines();
    }

    /**
     * Initialize map lines.
     */
    protected void initializeLines()
    {
        GridType type = getGameMap().getGeometry().getType();

        if (type == GridType.SQUARE) {
            initializeGridLines();
        } else if (type == GridType.HEXAGON) {
            initializeHexagonLines();
        } else if (type == GridType.DIAMOND) {
            initializeIsometricLines();
        } else {
            throw new UnsupportedOperationException("Unknown GeometryType: "
                + type);
        }
    }

    /**
     * Initialize grid lines.
     */
    protected void initializeGridLines()
    {
        int size = getGameMap().getGeometry().getSize();

        Graphics graphics = lines.getGraphics();

        // vertical lines
        for (int i = 0; i < lines.getWidth(); i += size) {
            graphics.drawLine(i, 0, i, lines.getHeight());
        }

        // horizontal lines
        for (int i = 0; i < lines.getHeight(); i += size) {
            graphics.drawLine(0, i, lines.getWidth(), i);
        }
    }

    /**
     * Initialize hexagon lines.
     */
    protected void initializeHexagonLines()
    {
        int size = getGameMap().getGeometry().getSize();

        Graphics graphics = lines.getGraphics();

        for (int i = 0; i < lines.getWidth(); i += size * 3) {
            for (int j = 0; j < lines.getHeight(); j +=
                (Math.cos(Math.PI * 1 / 6) * size) * 2) {

                // current row
                drawHexMapLine(graphics, i, j, size);

                // next row
                drawHexMapLine(graphics, i + (size * 3 / 2), (int) (j + Math
                    .cos(Math.PI * 1 / 6)
                    * size), size);
            }
        }
    }

    /**
     * Draw a hexagon map.
     * 
     * @param graphics Graphics
     * @param x X
     * @param y Y
     * @param size Size
     */
    protected void drawHexMapLine(Graphics graphics, int x, int y, int size)
    {
        int[] xs = new int[7];
        int[] ys = new int[7];

        xs[0] = xs[6] = x + (int) (Math.sin(Math.PI * 1 / 6) * size);
        ys[0] = ys[6] = y + (int) (Math.cos(Math.PI * 1 / 6) * size);

        xs[1] = x + size;
        ys[1] = y;

        xs[2] = x + (int) (Math.sin(Math.PI * 1 / 6) * size);
        ys[2] = y - (int) (Math.cos(Math.PI * 1 / 6) * size);

        xs[3] = x - (int) (Math.sin(Math.PI * 1 / 6) * size);
        ys[3] = y - (int) (Math.cos(Math.PI * 1 / 6) * size);

        xs[4] = x - size;
        ys[4] = y;

        xs[5] = x - (int) (Math.sin(Math.PI * 1 / 6) * size);
        ys[5] = y + (int) (Math.cos(Math.PI * 1 / 6) * size);

        // for (int i = 0; i < 6; i++) {
        // System.out.println("point[" + i + "]: (" + xs[i] + ","
        // + ys[i] + ")");
        // }

        graphics.drawPolyline(xs, ys, 7);
    }

    /**
     * Initialize isometric lines.
     */
    protected void initializeIsometricLines()
    {
        int size = getGameMap().getGeometry().getSize();

        Graphics graphics = lines.getGraphics();

        int ydiff = (int) (lines.getHeight() * Math.sin((Math.PI * 1 / 6)));

        // slash lines
        for (int i = 0; i < (lines.getHeight() + ydiff); i += size) {
            graphics.drawLine(0, i, lines.getWidth(), i - ydiff);
        }

        // backslash lines
        for (int i = roundDown(-ydiff, size); i < lines.getHeight(); i += size) {
            graphics.drawLine(0, i, lines.getWidth(), i + ydiff);
        }
    }

    /**
     * Round down to the nearest.
     * 
     * @param value Value
     * @param nearest Nearest
     * @return Value
     */
    private int roundDown(int value, int nearest)
    {
        int times = value / nearest;
        int left = value % nearest;

        return (times * nearest) - (left > 0 ? left : 0);
    }

    /**
     * Set game map.
     * 
     * @param gameMap Game map
     */
    public void setGameMap(IGameMap gameMap)
    {
        this.gameMap = gameMap;
    }

    /**
     * Get game map.
     * 
     * @return Game map
     */
    public IGameMap getGameMap()
    {
        return gameMap;
    }

    /**
     * Get preferred size.
     * 
     * @return Size
     * @see java.awt.Component#getPreferredSize()
     */
    public Dimension getPreferredSize()
    {
        return new Dimension(getGameMap().getGeometry().getWidth(),
            getGameMap().getGeometry().getHeight());
    }

    /**
     * Paint image.
     * 
     * @param graphics Graphics
     * @see java.awt.Component#paint(java.awt.Graphics)
     */
    public void paint(Graphics graphics)
    {
        graphics.drawImage(lines, 0, 0, null);
    }

    /**
     * Get preferred scrollable viewport size.
     * 
     * @return Dimension
     * @see javax.swing.Scrollable#getPreferredScrollableViewportSize()
     * @see Component#getPreferredSize()
     */
    public Dimension getPreferredScrollableViewportSize()
    {
        return getPreferredSize();
    }

    /**
     * Get scrollable tracks viewport height.
     * 
     * @return Tracks
     * @see javax.swing.Scrollable#getScrollableTracksViewportHeight()
     */
    public boolean getScrollableTracksViewportHeight()
    {
        return false;
    }

    /**
     * Get scrollable tracks viewport width.
     * 
     * @return Tracks
     * @see javax.swing.Scrollable#getScrollableTracksViewportWidth()
     */
    public boolean getScrollableTracksViewportWidth()
    {
        return false;
    }

    /**
     * Get scrollable unit increment.
     * 
     * @param visibleRect Visible rectangle
     * @param orientation Orientation
     * @param direction Direction
     * @return Increment
     * @see javax.swing.Scrollable#getScrollableUnitIncrement(Rectangle,int,int)
     */
    public int getScrollableUnitIncrement(Rectangle visibleRect,
        int orientation, int direction)
    {
        // Get the current position.
        int currentPosition = 0;
        if (orientation == SwingConstants.HORIZONTAL) {
            currentPosition = visibleRect.x;
        } else {
            currentPosition = visibleRect.y;
        }

        // Return the number of pixels between currentPosition
        // and the nearest tick mark in the indicated direction.
        if (direction < 0) {
            int newPosition =
                currentPosition - (currentPosition / maxUnitIncrement)
                    * maxUnitIncrement;
            return (newPosition == 0) ? maxUnitIncrement : newPosition;
        } else {
            return ((currentPosition / maxUnitIncrement) + 1)
                * maxUnitIncrement - currentPosition;
        }
    }

    /**
     * Get scrollable block increment.
     * 
     * @param visibleRect Visisble rectangle
     * @param orientation Orientation
     * @param direction Direction
     * @return Increment
     * @see javax.swing.Scrollable#getScrollableBlockIncrement(Rectangle,int,int)
     */
    public int getScrollableBlockIncrement(Rectangle visibleRect,
        int orientation, int direction)
    {
        if (orientation == SwingConstants.HORIZONTAL) {
            return visibleRect.width - maxUnitIncrement;
        } else {
            return visibleRect.height - maxUnitIncrement;
        }
    }
}
